/*
 * Copyright 2010 Christoph Widulle (christoph.widulle@googlemail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */



package com.android1.amarena2d.texture;

import com.android1.amarena2d.annotations.Incomplete;
import com.android1.amarena2d.commons.Provider;
import com.android1.amarena2d.commons.TextureRect;
import com.badlogic.gdx.graphics.Pixmap;

import java.util.ArrayList;

import static com.android1.amarena2d.commons.Tools.isPowerOfTwo;
import static com.android1.amarena2d.commons.Tools.nextPowerOfTwo;

//http://www.blackpawn.com/texts/lightmaps/default.html

@Incomplete
public class AutoPackTextureAtlas extends AbstractTextureAtlas implements TextureAtlas {

    private Pixmap fullPixmap;
    private Node rootNode;
    final ArrayList<Entry> entries;
    final Pixmap.Format format;
    final int size;
    final PixmapBuilder pixmapBuilder;

    public AutoPackTextureAtlas(int size) {
        this(size, Pixmap.Format.RGBA8888);
    }

    public AutoPackTextureAtlas(int size, Pixmap.Format format) {
        if (!isPowerOfTwo(size))
            size = nextPowerOfTwo(size);
        this.format = format;
        this.entries = new ArrayList<Entry>(50);
        this.size = size;
        this.pixmapBuilder = new PixmapBuilder(this);
        managedTexture = new ManagedPixmapTexture(pixmapBuilder);
        rootNode = new Node(0, 0, size, size);
    }

    public AtlasTextureFrame add(String tag, TextureDef textureDef) {
        Entry e = new Entry();
        e.textureDef = textureDef;
        e.index = nextIndex();
        e.atlasTextureFrame = new AtlasTextureFrameImpl(this, e.index, 0, 0, 0, 0);
        e.atlasTextureFrame.setTag(tag);
        super.add(e.atlasTextureFrame);
        entries.add(e);
        return e.atlasTextureFrame;
    }

    public AtlasTextureFrame add(TextureDef textureDef) {
        return this.add(null, textureDef);
    }


    private AtlasTextureFrame add(Entry e) {

        Pixmap pixmap;
        if (e.textureDef.isPixmap()) {
            pixmap = e.textureDef.pixmapProvider.get();
        } else {
            pixmap = engine.getTextureManager().getPixmap(e.textureDef.path, e.textureDef.getFileType());
        }

        Node node = rootNode.insert(pixmap);
        if (node != null) {
            node.index = e.index;
            final int x = node.rect.left;
            final int y = node.rect.top;
            final int width = pixmap.getWidth();
            final int height = pixmap.getHeight();
            fullPixmap.drawPixmap(pixmap, x, y, 0, 0, width, height);

            e.atlasTextureFrame.set(x, y, width, height);

            return e.atlasTextureFrame;

        } else {
            throw new IllegalStateException("Not enough space in atlas for pixmap." + pixmap);
        }
    }

    public Pixmap getFullPixmap() {
        return fullPixmap;
    }

    Pixmap build() {

        fullPixmap = engine.getGraphics().newPixmap(size, size, format);


        rootNode = new Node(0, 0, size, size);
        for (int i = 0; i < entries.size(); i++) {
            Entry entry = entries.get(i);
            add(entry);
        }
        return fullPixmap;
    }


    private final class Entry {
        int index;
        TextureDef textureDef;
        AtlasTextureFrame atlasTextureFrame;

    }

    private final class Node {

        public static final int EMPTY = -1;
        final Node[] child = new Node[2];
        final TextureRect rect;
        int index = EMPTY;

        public Node(int left, int top, int right, int bottom) {
            rect = new TextureRect(left, top, right, bottom);
        }

        public Node insert(Pixmap pixmap) {

            if (!isLeaf()) {
                final Node newNode = child[0].insert(pixmap);
                if (newNode != null)
                    return newNode;
                else {
                    return child[1].insert(pixmap);
                }

            } else {
                if (!isEmpty())
                    return null;

                if (!fits(pixmap))
                    return null;

                if (excatMatch(pixmap))
                    return this;


                final int dw = rect.getWidth() - pixmap.getWidth();
                final int dh = rect.getHeight() - pixmap.getHeight();

                if (dw > dh) {

                    child[0] = new Node(rect.left, rect.top, rect.left + pixmap.getWidth(), rect.bottom);
                    child[1] = new Node(rect.left + pixmap.getWidth(), rect.top, rect.right, rect.bottom);

                } else {

                    child[0] = new Node(rect.left, rect.top, rect.right, rect.top + pixmap.getHeight());
                    child[1] = new Node(rect.left, rect.top + pixmap.getHeight(), rect.right, rect.bottom);

                }
                return child[0].insert(pixmap);

            }
        }

        public boolean isEmpty() {
            return index == EMPTY;
        }

        public boolean excatMatch(Pixmap pixmap) {
            return this.rect.matches(pixmap.getWidth(), pixmap.getHeight());
        }

        public boolean fits(Pixmap pixmap) {
            return this.rect.fits(pixmap.getWidth(), pixmap.getHeight());
        }

        public boolean isLeaf() {
            return child[0] == null && child[1] == null;
        }

        public int getIndex() {
            return index;
        }


    }

    public static final class PixmapBuilder implements Provider<Pixmap> {

        AutoPackTextureAtlas autoPackTextureAtlas;

        public PixmapBuilder(AutoPackTextureAtlas autoPackTextureAtlas) {
            this.autoPackTextureAtlas = autoPackTextureAtlas;
        }

        @Override
        public Pixmap get() {
            if (autoPackTextureAtlas.fullPixmap == null)
                autoPackTextureAtlas.fullPixmap = autoPackTextureAtlas.build();
            return autoPackTextureAtlas.fullPixmap;
        }
    }

}